#include "dicom/upload/same_hash_checker.h"

#include <surbasic/CJsonParse.h>
#include <surbasic/FileKeys.h>
#include <surbasic/surbasic.h>
#include <surbasic/FileUploaderEnc.h>
#include "dicom/storage/encrypt.h"

namespace dicom {

using namespace SDBasic::code;
using SDBasic::code::SD_CODE;

static bool ServerHashEntryMatch(const ServerHashEntry& se,
                                 const HashEntry& h) {
  if (se.hexkey == h.hexkey && se.src_size == h.src_size &&
      se.enc_digest == h.enc_digest && se.enc_size == h.enc_size) {
    return true;
  }
  return false;
}

SameHashChecker::SameHashChecker(HashCache* hcache) : hcache_(hcache) {}
SameHashChecker::~SameHashChecker() {}

Status ParseServerHashEntryList(const std::vector<SameHashFileInfo>* encInfo,
                                const std::string& sha512,
                                ServerHashEntryList* list) {
  for (size_t i = 0; i < encInfo->size(); ++i) {
    const SameHashFileInfo& fi = (*encInfo)[i];

    ServerHashEntry info;
    info.src_size = fi.size;
    std::string errmsg;
    bool ok = SDBasic::DecryptStorageKeyFromShareKey(fi.shareKey, sha512,
                                                     &info.hexkey, &errmsg);
    if (!ok) {
      LOG_ERROR << "failed to decrypt sharekey: " << fi.shareKey << " : "
                << errmsg;
      continue;
    }
    info.enc_digest = fi.enc_hash;
    info.enc_size = fi.enc_size;
    list->push_back(info);
  }

  return Status();
}

Status SameHashChecker::GetServerHashEntryListFromServer(
    uint64_t surdoc_key, const std::string& digest, const std::string& sha512,
    ServerHashEntryList* list) {
  std::vector<SameHashFileInfo> encInfo;
  SD_CODE c = SDBasic::GetSameHashList(surdoc_key, digest, &encInfo);
  if (c != code::SD_SUCCESSED && c != code::SD_FILE_NEED_UPLOAD) {
    return Status(Status::kOther, "failed to get same hash list from server");
  }

  Status s = ParseServerHashEntryList(&encInfo, sha512, list);
  if (!s.ok()) {
    LOG_ERROR << "invalid same hash list";
    return s;
  }

  return s;
}

bool SameHashChecker::CanRapidUploadFastCheck(const ServerHashEntryList* list,
                                              const std::string& src_md5,
                                              HashEntry* h) {
  // We can't rapid upload if there is no file with same hash
  if (list->empty()) return false;

  // Since we has already encrypt the file locally, we can use
  // this info to do a fast check without encryption
  for (size_t i = 0; i < list->size(); ++i) {
    const ServerHashEntry& se = (*list)[i];
    HashEntry hash;
    if (hcache_->GetHashEntry(src_md5, se.hexkey, &hash) == false) {
      continue;
    }

    if (ServerHashEntryMatch(se, hash)) {
      hash.status = HASH_IS_EXISTS;
      hcache_->UpdateHashEntry(&hash);
      *h = hash;
      return true;
    }
  }

  // No matches, We need re-encrypt the file using the hexkey
  // from ServerHashEntry
  return false;
}

bool SameHashChecker::CanRapidUploadSlowCheck(const ServerHashEntryList* list,
                                              const std::string& src_file,
                                              HashEntry* hash) {
  for (size_t i = 0; i < list->size(); ++i) {
    const ServerHashEntry* e = &(*list)[i];

    FileReader r;
    NullWriter w;
    Encryptor enc;
    Status s;

    s = r.Open(src_file);
    if (!s.ok()) {
      LOG_ERROR << "failed to open src file: " << src_file;
      break;
    }

    HashEntry tmp_hash;
    s = enc.DoEncryt(e->hexkey, &r, &w, &tmp_hash);
    if (!s.ok()) {
      continue;
    }

    assert(tmp_hash.IsComplete());

    r.Close();
    w.Close();

    if (ServerHashEntryMatch(*e, tmp_hash)) {
      tmp_hash.status = HASH_IS_EXISTS;
      hcache_->UpdateHashEntry(&tmp_hash);
      *hash = tmp_hash;
      return true;
    }
  }

  return false;
}
}
